package object2D;

import context.*;

import mainFrame.MainFrame;
import model2D.Model2D;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.BasicStroke;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Rectangle2D;
import java.awt.geom.GeneralPath;
import java.awt.image.ImageObserver;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

import javax.swing.border.LineBorder;
import javax.swing.event.ChangeListener;

import org.json.JSONObject;
import org.json.JSONException;

import util.FillStyle;
import util.FontStyle;
import util.PixelPoint;
import util.PixelPointSet;
import util.StrokeStyle;
import util.Util2;
import util.ValueType;

// ********************************************************************
// *** CLASS: Object2D
// ********************************************************************
public abstract class Object2D extends ValueType implements Context.Constants
{ //private static final boolean DEBUG=true; // Overriding Context.Constants.DEBUG
  static final String TAG="object2D.Object2D";
 
  protected StrokeStyle.StrokeDialog currentStrokeDialog;
  protected FillStyle.FillDialog currentFillDialog;
  protected FontStyle.FontDialog currentFontDialog;
	
  private static final String[] fieldNames={"Type","Ident","East","North","Json String"};
  private static final int TYPE_FIELD=0;
  private static final int IDENT_FIELD=1;
  private static final int X0_FIELD=2;
  private static final int Y0_FIELD=3;
  private static final int PATH_FIELD=4;

  protected Model2D objectModel;
  public abstract JSONObject toJSONObject() throws JSONException;
  
  public String getJsonString()
  { try { return(toJSONObject().toString()); } catch(JSONException e) { e.printStackTrace(); }
    return("FAILED");
  }

  private String IDENT="";
  private PixelPointSet pointSet=new PixelPointSet();
  protected FontStyle font;
  protected StrokeStyle stroke;
  protected FillStyle fill;
  private boolean CLOSED_PATH;
  
  public String getIdent() { return(IDENT); }
  protected boolean getClosedPath() { return(CLOSED_PATH); }

  public void setIdent(String newValue)
  { if(DEBUG) Log.d(TAG,"setIdent: IDENT="+IDENT+", newValue="+newValue);
    if(IDENT==null) IDENT="";
	  if(!IDENT.equals(newValue))
  { String oldValue=IDENT; IDENT=newValue; FIRE_CHANGE_EVENT("IDENT["+oldValue+"==>"+newValue+"]"); } }
  public void setClosedPath(boolean newValue)
  { if(CLOSED_PATH!=newValue) { CLOSED_PATH=newValue; FIRE_CHANGE_EVENT("CLOSED_PATH"); } }
  
  public void add(ChangeListener changeListener)
  {	super.add(changeListener);
//    pointSet.add(changeListener);
    pointSet.addPointListener(changeListener);
	font.add(changeListener);
	stroke.add(changeListener);
	fill.add(changeListener);
  }
  public void remove(ChangeListener changeListener)
  { super.remove(changeListener);
//    pointSet.remove(changeListener);
    pointSet.removePointListener(changeListener);
	font.remove(changeListener);
	stroke.remove(changeListener);
	fill.remove(changeListener);
  }

  
  // ******************************************************************
  // *** CONSTRUCTOR: DrawObject
  // ******************************************************************
  private static int SEQU=0;
  public Object2D(Model2D objectModel,String id) throws JSONException
  { this.objectModel=objectModel;
    if(id!=null) setIdent(id);
    else setIdent("Untitled"+(++SEQU));
    font=MainFrame.getCurrentFont().copy();
    stroke=MainFrame.getCurrentStroke().copy();
    fill=MainFrame.getCurrentFill().copy();
  }
  
  public void clearPointSet() { pointSet.clearPointSet(); }
  public int numberOfPoints() { return(pointSet.numberOfPoints()); }
  public void add(PixelPoint location)  { pointSet.add(location); }

  public PixelPoint getMapLocation(int i) { return(pointSet.getMapLocation(i)); }
  public void setMapLocation(PixelPoint location,int i) { pointSet.setMapLocation(location,i); }

  public static String[] getFieldNames() { return(fieldNames); }

  public Object getField(int i)
  { switch(i)
	{ case TYPE_FIELD: return(getClassName());
      case IDENT_FIELD: return(getIdent());
//      case X0_FIELD: return(""+getMapLocation(0).getEast());
//      case Y0_FIELD: return(""+getMapLocation(0).getNorth());
      case X0_FIELD: return(""+getMapLocation(0).getX());
      case Y0_FIELD: return(""+getMapLocation(0).getY());
      case PATH_FIELD: return(getJsonString());
	}
    return(null);
  }
  

  public void setField(int i,Object value)
  { try
    {  switch(i)
	  { case TYPE_FIELD: break; // NOT EDITABLE
        case IDENT_FIELD: setIdent((String)value); break;
//        case X0_FIELD: getMapLocation(0).setEast(Double.parseDouble((String)value)); break;
//        case Y0_FIELD: getMapLocation(0).setNorth(Double.parseDouble((String)value)); break;
        case X0_FIELD: getMapLocation(0).setX(Double.parseDouble((String)value)); break;
        case Y0_FIELD: getMapLocation(0).setY(Double.parseDouble((String)value)); break;
        case PATH_FIELD: break; // NOT EDITABLE
	  }
    } catch(Throwable ignore){}
  }

//  public abstract void edit();
//  public abstract void paint(Graphics2D g);
  public abstract JPanel getEditGeneralPanel(String title);

  
  // ******************************************************************
  // *** METHOD: move
  // ******************************************************************
  public void move(double dx,double dy)
  { int n=numberOfPoints();
    for(int i=0;i<n;i++)
    { PixelPoint p=getMapLocation(i);
      p.setX(p.getX()+dx);
      p.setY(p.getY()+dy);
    }
  }

//  public GeneralPath getPath() { return(getPath(0,0)); }
//  protected GeneralPath getPath(double dx,double dy)
  public Shape getShape() { return(getShape(0,0)); }
  protected Shape getShape(double dx,double dy)
  { int n=numberOfPoints(); if(n<2) return(null);
    PixelPoint p0=getMapLocation(0);
    GeneralPath path=new GeneralPath();
    path.moveTo(p0.getX()+dx,p0.getY()+dy);
    for(int i=1;i<n;i++)
    { PixelPoint p=getMapLocation(i);
      path.lineTo(p.getX()+dx,p.getY()+dy);
    }
    if(getClosedPath()) path.closePath();
    return(path);	  
  }

  public GeneralPath getPath(int i)
  { PixelPoint p=getMapLocation(i);
    PixelPoint q=getMapLocation(i+1);
    GeneralPath path=new GeneralPath();
    path.moveTo(p.getX(),p.getY());
    path.lineTo(q.getX(),q.getY());
    return(path);	  
  }
  
  public void paint(Graphics2D g,ImageObserver imageObserver)
  { Util2.paint(g,getShape(),fill,stroke); }

  public void paintContour(Graphics2D g,double dx,double dy)
  { Shape shape=getShape(dx,dy); if(shape==null) return;
    Util2.draw(g,shape,Color.black,new BasicStroke(1));
  }
  
  public boolean contains(PixelPoint p)
  { if(getClosedPath())
    { Shape shape=getShape(); if(shape==null) return(false);
	  return(shape.contains(p.getX(),p.getY()));
//	  return(shape.intersects(p.getX(),p.getY(),5,5));
    }
    int n=numberOfPoints()-1; if(n<1) return(false);
    for(int i=0;i<n;i++)
    { if(getPath(i).intersects(p.getX(),p.getY(),5,5)) return(true); }
    return(false);
  }

  public PointMark[] getPointMarks()
  { int n=numberOfPoints();
	PointMark[] marks=new PointMark[n];
	for(int i=0;i<n;i++)
	{ marks[i]=new PointMark(i);
	}
    return(marks);
  }

  
  // ****************************************************************
  // *** INNER CLASS: PointMark
  // ****************************************************************
  public class PointMark
  { int pointNumber;
	public PointMark(int pointNumber) { this.pointNumber=pointNumber; } 
	public PixelPoint getMapLocation() { return(Object2D.this.getMapLocation(pointNumber)); }
	public void paint(Graphics2D g)
	{ PixelPoint p=getMapLocation();
	  g.draw(new Rectangle2D.Double(p.getX()-4,p.getY()-4,8,8));
	}
	public void moveTo(double x,double y)
	{ PixelPoint mapLocation=getMapLocation();
	  mapLocation.setX(x); mapLocation.setY(y);
	}
    public void edit()
    { if(DEBUG) Log.d(TAG,"PointMark.edit: ident="+getIdent());
      final PixelPoint location=getMapLocation();
//      final double oldEast=location.getEast();
//      final double oldNorth=location.getNorth();
      final double oldX=location.getX();
      final double oldY=location.getY();
      final JDialog dialog=new JDialog();
      dialog.setBounds(800,200,270,120); // Popup at: x,y,width,height
      dialog.addWindowListener(new WindowAdapter()
      { public void windowClosing(WindowEvent we)
//      { location.setEast(oldEast); location.setNorth(oldNorth);
        { location.setX(oldX); location.setY(oldY);
          MainFrame.repaint(); dialog.dispose();
        }
      });
      dialog.setTitle("Justify Location");
      dialog.getContentPane().setLayout(new BorderLayout());
      dialog.getContentPane().add(location.getSpinnerPanel(),BorderLayout.CENTER);

      JButton okBtn=new JButton("Ok");
      okBtn.addActionListener(new ActionListener()
      { public void actionPerformed(ActionEvent e)
        { dialog.dispose(); } });
      JButton cancelBtn=new JButton("Cancel");
      cancelBtn.addActionListener(new ActionListener()
      { public void actionPerformed(ActionEvent e)
//      { location.setEast(oldEast); location.setNorth(oldNorth);
        { location.setX(oldX); location.setY(oldY);
          MainFrame.repaint(); dialog.dispose();
        }
      });
      JPanel buttons=new JPanel();
      buttons.add(okBtn); buttons.add(cancelBtn);
      dialog.getContentPane().add(buttons,BorderLayout.SOUTH);
      dialog.setAlwaysOnTop(true);
      dialog.setVisible(true);
    }
  }
  
  //********************************************************************
  //*** INNER CLASS: EditDialog
  //********************************************************************
  public void edit() { new EditDialog(); }
  private class EditDialog extends EditValueTypeDialog
  { static final long serialVersionUID=0; // AD'HOC
    private JPanel centerPanel;
    private JPanel buttonPanel;
    
    public void cancel()
    { if(currentStrokeDialog!=null) { currentStrokeDialog.cancel(); currentStrokeDialog=null; }
      if(currentFillDialog!=null)   { currentFillDialog.cancel(); currentFillDialog=null; }
      if(currentFontDialog!=null)   { currentFontDialog.cancel(); currentFontDialog=null; }
      super.cancel();	
    }
    
    public void close()
    { if(currentStrokeDialog!=null) { currentStrokeDialog.close(); currentStrokeDialog=null; }
      if(currentFillDialog!=null)   { currentFillDialog.close(); currentFillDialog=null; }
      if(currentFontDialog!=null)   { currentFontDialog.close(); currentFontDialog=null; }
      super.close();	
    }
  
    // Initial Values:
    private String initialName;
    private FontStyle initialFont;
    private StrokeStyle initialStroke;
    private FillStyle initialFill;
    private boolean initialClosedPath;
    protected void saveInitialValues()
    { initialName=getName();
      initialFont=font.copy();
      initialStroke=stroke.copy();
      initialFill=fill.copy();
      initialClosedPath=getClosedPath();
    }
    protected void restoreInitialValues()
    { setName(initialName);
      font=initialFont;
      stroke=initialStroke;
      fill=initialFill;
      setClosedPath(initialClosedPath);
      MainFrame.repaint();
    }

    // ******************************************************************
    // *** CONSTRUCTOR: EditDialog
    // ******************************************************************
    public EditDialog()
    { super(Object2D.this,"Edit "+getClassName()+" Attributes");
      setBounds(800,200,370,320); // Popup at: x,y,width,height

      centerPanel=new JPanel();
      centerPanel.add(getEditGeneralPanel("General"));
      centerPanel.add(new JsonStringPanel("Json String"));
      
      buttonPanel=new JPanel();
      JButton okBtn=new JButton("Ok");
      buttonPanel.add(okBtn);
      okBtn.addActionListener(new ActionListener()
      { public void actionPerformed(ActionEvent e) { close(); } });
      JButton resetBtn=new JButton("Reset");
      buttonPanel.add(resetBtn);
      resetBtn.addActionListener(new ActionListener()
      { public void actionPerformed(ActionEvent e) { cancel(); } });
      JButton deleteBtn=new JButton("Delete");
      buttonPanel.add(deleteBtn);
      deleteBtn.addActionListener(new ActionListener()
      { public void actionPerformed(ActionEvent e)
        { objectModel.removeChild(Object2D.this);
          MainFrame.getCurrentDrawPanel().setSelected(null);
          close();
        }
      });
      
      setLayout(new BorderLayout());
      add(new JLabel(" "),BorderLayout.NORTH); // Space only
      add(centerPanel,BorderLayout.CENTER);
      add(buttonPanel,BorderLayout.SOUTH);
      setAlwaysOnTop(true);
      setVisible(true);
    }
  }
  
  protected JButton addEditFontButton(JPanel panel)
  { panel.add(new JLabel("Font:"));

	JButton fontButton=new JButton("Press to Edit");
//	fontButton.setBackground(font.getColor());
	fontButton.addActionListener(new ActionListener()
    { public void actionPerformed(ActionEvent e)
      { if(currentFontDialog!=null) currentFontDialog.cancel();
        currentFontDialog=font.new FontDialog(stroke,false);	}
    });
    panel.add(fontButton);
    return(fontButton);
  }
  
  protected JButton addEditFillButton(JPanel panel,boolean withCheckBox)
  { if(withCheckBox)
    { JCheckBox fillBox=new JCheckBox("Fill:",null,fill.getEnabled());
      fillBox.addActionListener(new ActionListener()
      { public void actionPerformed(ActionEvent e)
        { fill.setEnabled(!fill.getEnabled());
          MainFrame.repaint();        	
        }
      });
      panel.add(fillBox);
    } else panel.add(new JLabel("Fill:")); 
    JButton  fillButton=new JButton("Press to Edit");
    fillButton.setBackground(fill.getColor());
    fillButton.addActionListener(new ActionListener()
    { public void actionPerformed(ActionEvent e)
      { if(currentFillDialog!=null) currentFillDialog.cancel();
        currentFillDialog=fill.new FillDialog(stroke,false); }
    });
    panel.add(fillButton);
	return(fillButton);  
  }
  
  protected JButton addEditStrokeButton(JPanel panel,boolean withCheckBox)
  { if(withCheckBox)
    { JCheckBox strokeBox=new JCheckBox("Stroke:",null,stroke.getEnabled());
      strokeBox.addActionListener(new ActionListener()
      { public void actionPerformed(ActionEvent e)
        { stroke.setEnabled(!stroke.getEnabled());
          MainFrame.repaint();        	
        }
      });
      panel.add(strokeBox);
    } else  panel.add(new JLabel("Stroke:"));
  

	JButton strokeButton=new JButton("Press to Edit");
    strokeButton.setBackground(stroke.getColor());
    strokeButton.addActionListener(new ActionListener()
    { public void actionPerformed(ActionEvent e)
      { if(currentStrokeDialog!=null) currentStrokeDialog.cancel();
        currentStrokeDialog=stroke.new StrokeDialog(false);	}
    });
    panel.add(strokeButton);
    return(strokeButton);
  }

  // ****************************************************************
  // *** INNER CLASS: JsonStringPanel
  // ****************************************************************
  private class JsonStringPanel extends JPanel
  { static final long serialVersionUID=0; // AD'HOC
    private JTextArea textArea;
    
    public void paint(Graphics g)
    { textArea.setText(getJsonString().trim());
  	  super.paint(g);  
    }
  
    JsonStringPanel(String title)
    { setBorder(Util2.getTitleBorder(title));
      textArea=new JTextArea(1,1);
      textArea.setBorder(new LineBorder(Color.gray));
      textArea.setLineWrap(true);
      textArea.setWrapStyleWord(true);
      textArea.setText(getJsonString().trim());
      textArea.setEditable(false);
      JScrollPane scrollPane=new JScrollPane(textArea);
      scrollPane.setPreferredSize(new Dimension(330,70));
      add(scrollPane);
    }
  }
  
}
